package com.cmc.cloud.cmclink.doc.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.map.MapUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.doc.api.document.emums.EmImTypeEnum;
import com.cmc.cloud.cmclink.doc.api.document.emums.SoStatusEnum;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.VoyageBookingTimeErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.entity.SoDO;
import com.cmc.cloud.cmclink.doc.entity.VoyageBookingTimeDO;
import com.cmc.cloud.cmclink.doc.mapper.SoMapper;
import com.cmc.cloud.cmclink.doc.mapper.VoyageBookingTimeMapper;
import com.cmc.cloud.cmclink.doc.rpcservice.VoyScheduleService;
import com.cmc.cloud.cmclink.doc.service.VoyageBookingTimeService;
import com.cmc.cloud.cmclink.doc.util.EmailUtil;
import com.cmc.cloud.cmclink.doc.vo.voyagebookingtimevo.*;
import com.cmc.cloud.cmclink.svl.api.base.enums.DateKindEnum;
import com.cmc.cloud.cmclink.svl.api.base.enums.SystemEnums;
import com.cmc.cloud.cmclink.svl.api.base.req.VoyQueryReqDTO;
import com.cmc.cloud.cmclink.svl.api.base.resp.VoyQueryRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 航次截止时间 Service 实现类
 *
 * @author 招商轮船-管理员
 */
@Service
@Validated
public class VoyageBookingTimeServiceImpl implements VoyageBookingTimeService {

    @Resource
    private VoyageBookingTimeMapper voyageBookingTimeMapper;

    @Resource
    private VoyScheduleService voyScheduleService;
    @Resource
    private SoMapper soMapper;
    @Resource
    private EmailUtil emailUtil;
    @Resource
    ThreadPoolTaskExecutor taskExecutor;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "setupWeightLimitedUnique", keys = {"#createReqVO.getVesselCode", "#createReqVO.getVoyage"
            , "#createReqVO.getExImType", "#createReqVO.getPodCode"})
    public void createVoyageBookingTime(VoyageBookingTimeCreateReqVO createReqVO) {
        // 插入
    /*    validateUnique(createReqVO, null);
        List<VoyageBookingTimeDO> voyageBookingTime/**
  * 属性转换 CONVERT
  * = new ArrayList<>();
        for (VoyageBookingTimeBaseVO.TimeInfo timeInfo : createReqVO.getTimeInfos()) {
            VoyageBookingTimeDO voyageBookingTime = BeanUtil.copyProperties(createReqVO, VoyageBookingTimeDO.class);
            voyageBookingTime.setTimeType(timeInfo.getTimeType());
            voyageBookingTime.setTimeValue(timeInfo.getTimeValue());
            voyageBookingTimeDOS.add(voyageBookingTime);
        }
        voyageBookingTimeMapper.insertBatch(voyageBookingTimeDOS);*/
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "setupWeightLimitedUnique", keys = {"#updateReqVO.getVesselCode", "#updateReqVO.getVoyage"
            , "#updateReqVO.getExImType", "#updateReqVO.getPodCode"})
    public void updateVoyageBookingTime(VoyageBookingTimeUpdateReqVO updateReqVO) {
        // 校验存在
        List<Long> ids = updateReqVO.getTimeInfos().stream().filter(entity -> entity.getId() != null).map(entity -> entity.getId()).collect(Collectors.toList());
        List<VoyageBookingTimeDO> updateObjs = new ArrayList<>();
        LambdaQueryWrapperX<VoyageBookingTimeDO> queryWrapperX = new LambdaQueryWrapperX<VoyageBookingTimeDO>()
                .eqIfPresent(VoyageBookingTimeDO::getVesselCode, updateReqVO.getVesselCode())
                .eqIfPresent(VoyageBookingTimeDO::getVoyage, updateReqVO.getVoyage())
                .eqIfPresent(VoyageBookingTimeDO::getExImType, updateReqVO.getExImType())
                .eqIfPresent(VoyageBookingTimeDO::getPodCode, updateReqVO.getPodCode());
        Map<String, VoyageBookingTimeDO> voyageBookingTimeOldMap = voyageBookingTimeMapper.selectList(queryWrapperX).stream().collect(Collectors.toMap(e -> e.getTimeType(), e -> e));
        // 数据变动，需要发送邮件
        boolean checkChange = false;
        Map<String, Object> sendEmailParameters = new HashMap<>(8);
        sendEmailParameters.put("vessel_code", updateReqVO.getVesselCode());
        sendEmailParameters.put("voyage", updateReqVO.getVoyage());
        sendEmailParameters.put("pod_code", updateReqVO.getPodCode());
        // 更新或者创建
        for (VoyageBookingTimeBaseVO.TimeInfo timeInfo : updateReqVO.getTimeInfos()) {
            VoyageBookingTimeDO voyageBookingTime = BeanUtil.copyProperties(updateReqVO, VoyageBookingTimeDO.class);
            voyageBookingTime.setTimeType(timeInfo.getTimeType());
            voyageBookingTime.setTimeValue(timeInfo.getTimeValue());
            sendEmailParameters.put(timeInfo.getTimeType() + "_new", timeInfo.getTimeValue());
            VoyageBookingTimeDO oldTime = voyageBookingTimeOldMap.get(timeInfo.getTimeType());
            if (timeInfo.getId() != null) {
                voyageBookingTime.setId(timeInfo.getId());
                if (oldTime == null || Objects.equals(oldTime.getId(), timeInfo.getId()) == false) {
                    throw ServiceExceptionUtil.exception(VoyageBookingTimeErrorCodeConstants.VOYAGE_BOOKING_TIME_EXISTED);
                }
                if (Objects.equals(oldTime.getTimeValue(), timeInfo.getTimeValue()) == false) {
                    checkChange = true;
                }
                // 旧数据
                sendEmailParameters.put(oldTime.getTimeType(), oldTime.getTimeValue());
            } else {
                if (oldTime != null) {
                    throw ServiceExceptionUtil.exception(VoyageBookingTimeErrorCodeConstants.VOYAGE_BOOKING_TIME_EXISTED);
                }
                checkChange = true;
            }
            updateObjs.add(voyageBookingTime);
        }
        voyageBookingTimeMapper.saveOrUpdateBatch(updateObjs);
        if (checkChange) {
            LambdaQueryWrapperX<SoDO> querySoWrapper = new LambdaQueryWrapperX<SoDO>();
            querySoWrapper.eq(SoDO::getBookingVessel, updateReqVO.getVesselCode());
            querySoWrapper.eq(SoDO::getBookingVoyage, updateReqVO.getVoyage());
            querySoWrapper.eq(SoDO::getSoStatus, SoStatusEnum.BookingConfirmed.getValue());
            querySoWrapper.eq(SoDO::getPolCode, updateReqVO.getPodCode());
            querySoWrapper.eq(SoDO::getValidStatus, 1);
            querySoWrapper.orderByDesc(SoDO::getBookingNo);
            List<SoDO> soList = soMapper.selectList(querySoWrapper);
            Map<String, List<String>> sameEmailMap = new HashMap<String, List<String>>(16);
            if (CollectionUtil.isNotEmpty(soList)) {
                for (SoDO so : soList) {
                    String bookingNo = so.getBookingNo();
                    String[] soNotifyEmailsList = so.getSoNotifyEmails().split(";");
                    for (String email : soNotifyEmailsList) {
                        email = StringUtils.lowerCase(email);
                        List<String> bookingNoList = sameEmailMap.get(email);
                        if (bookingNoList == null) {
                            bookingNoList = new ArrayList<>(8);
                            sameEmailMap.put(email, bookingNoList);
                        }
                        bookingNoList.add(bookingNo);
                    }
                }
            }
            if (CollectionUtil.isNotEmpty(sameEmailMap)) {
                for (Map.Entry<String, List<String>> email : sameEmailMap.entrySet()) {
                    if (Validator.isEmail(email.getKey())) {
                        sendEmailParameters.put("booking_nos", StringUtils.join(email.getValue(), " "));
                        taskExecutor.execute(() -> {
                            emailUtil.sendMail(email.getKey(), "bookingTimeChange", sendEmailParameters);
                        });
                    }
                }
            }
        }
    }


    @Override
    public void deleteVoyageBookingTime(Long id) {
        // 校验存在
        validateVoyageBookingTimeExists(id);
        // 删除
        voyageBookingTimeMapper.deleteById(id);
    }

    private void validateVoyageBookingTimeExists(Long id) {
        if (voyageBookingTimeMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(VoyageBookingTimeErrorCodeConstants.VOYAGE_BOOKING_TIME_NOT_EXISTS);
        }
    }

    @Override
    public VoyageBookingTimeDO getVoyageBookingTime(Long id) {
        return voyageBookingTimeMapper.selectById(id);
    }

    @Override
    public List<VoyageBookingTimeDO> getVoyageBookingTimeList(Collection<Long> ids) {
        return voyageBookingTimeMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<VoyageBookingTimeDO> getVoyageBookingTimePage(VoyageBookingTimePageReqVO reqVO) {
        IPage<VoyageBookingTimeDO> page = Page.of(reqVO.getPageNo(), reqVO.getPageSize());

        IPage<VoyageBookingTimeDO> result = voyageBookingTimeMapper.selectPageList(page, reqVO);
        return new PageResult<VoyageBookingTimeDO>(result.getRecords(), result.getTotal());
    }

    @Override
    public List<VoyageBookingTimeRespVO> queryVoyageBookingTime(VoyageBookingTimeQueryVO bookingTimeQueryVO) {
        VoyQueryReqDTO voyQueryReqDTO = new VoyQueryReqDTO();
        voyQueryReqDTO.setDateKind(DateKindEnum.SEARCH_PERFORM.getLabel());
        voyQueryReqDTO.setDateType(EmImTypeEnum.EXPORT.getValue().equals(bookingTimeQueryVO.getExImType()) ? "ETD" : "ETA");
        voyQueryReqDTO.setStartDate(bookingTimeQueryVO.getStartTime());
        voyQueryReqDTO.setEndDate(bookingTimeQueryVO.getEndTime());
        voyQueryReqDTO.setLaneCode(bookingTimeQueryVO.getLaneCode());
        voyQueryReqDTO.setVesselCode(StringUtils.isNotEmpty(bookingTimeQueryVO.getVesselCode()) ? Arrays.asList(bookingTimeQueryVO.getVesselCode()) : null);
        voyQueryReqDTO.setPortCode(bookingTimeQueryVO.getPodCode());
        //进出口标识
        boolean flag = EmImTypeEnum.EXPORT.getValue().equals(bookingTimeQueryVO.getExImType());
        if (flag) {
            voyQueryReqDTO.setExVoy(bookingTimeQueryVO.getVoyage());
            voyQueryReqDTO.setImVoy("");
        } else {
            voyQueryReqDTO.setImVoy(bookingTimeQueryVO.getVoyage());
            voyQueryReqDTO.setExVoy("");
        }
        voyQueryReqDTO.setSystemNumber(SystemEnums.DOC.getSystemNumber());
        List<VoyQueryRespDTO> checkedData = voyScheduleService.listByCondition(voyQueryReqDTO);
        List<VoyageBookingTimeRespVO> respVOList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(checkedData)) {
            Set<String> vesselCodes = checkedData.stream().map(VoyQueryRespDTO::getVesselCode).collect(Collectors.toSet());
            Set<String> voyages;
            if (flag) {
                voyages = checkedData.stream().map(VoyQueryRespDTO::getExVoy).collect(Collectors.toSet());
            } else {
                voyages = checkedData.stream().map(VoyQueryRespDTO::getImVoy).collect(Collectors.toSet());
            }
            List<VoyageBookingTimeDO> voyageBookingTimeDos = voyageBookingTimeMapper.selectList(new LambdaQueryWrapperX<VoyageBookingTimeDO>()
                    .eq(VoyageBookingTimeDO::getPodCode, bookingTimeQueryVO.getPodCode())
                    .eq(VoyageBookingTimeDO::getExImType, bookingTimeQueryVO.getExImType())
                    .inIfPresent(VoyageBookingTimeDO::getVesselCode, vesselCodes)
                    .inIfPresent(VoyageBookingTimeDO::getVoyage, voyages));
            Map<String, List<VoyageBookingTimeDO>> dataMap = MapUtil.newHashMap();
            if (CollectionUtil.isNotEmpty(voyageBookingTimeDos)) {
                dataMap = voyageBookingTimeDos.stream().collect(Collectors.groupingBy(
                        vo -> vo.getExImType() + "_" + vo.getPodCode() + "_" + vo.getVesselCode() + "_" + vo.getVoyage()));
            }
            for (VoyQueryRespDTO entity : checkedData) {
                VoyageBookingTimeRespVO timeRespVO = new VoyageBookingTimeRespVO();
                timeRespVO.setLaneCode(entity.getLaneCode());
                timeRespVO.setExImType(bookingTimeQueryVO.getExImType());
                timeRespVO.setPodCode(bookingTimeQueryVO.getPodCode());
                timeRespVO.setVesselCode(entity.getVesselCode());
                timeRespVO.setEtdOrEta(flag ? entity.getPtd() : entity.getPta());
                timeRespVO.setVoyage(flag ? entity.getExVoy() : entity.getImVoy());
                //已配置时间列表封装
                if (CollectionUtil.isNotEmpty(dataMap)) {
                    List<VoyageBookingTimeDO> groupList = dataMap.get(timeRespVO.getExImType() + "_" + timeRespVO.getPodCode() +
                            "_" + timeRespVO.getVesselCode() + "_" + timeRespVO.getVoyage());
                    List<VoyageBookingTimeBaseVO.TimeInfo> timeInfos = BeanUtil.copyToList(groupList, VoyageBookingTimeBaseVO.TimeInfo.class);
                    timeRespVO.setTimeInfos(timeInfos);
                }
                respVOList.add(timeRespVO);
            }
        }
        return respVOList;
    }

    @Override
    public VoyageBookingTimeDO getVoyageBookingTimeByVesselCodeAndVoyage(String vesselCode, String voyage, String polCode, String exImType, String timeType, String timeValue) {
        VoyageBookingTimeDO voyageBookingTime = voyageBookingTimeMapper.selectOne(new LambdaQueryWrapperX<VoyageBookingTimeDO>()
                .eq(VoyageBookingTimeDO::getVesselCode, vesselCode)
                .eq(VoyageBookingTimeDO::getVoyage, voyage)
                .eq(VoyageBookingTimeDO::getPodCode, polCode)
                .eq(VoyageBookingTimeDO::getExImType, exImType)
                .eq(VoyageBookingTimeDO::getTimeType, timeType)
                .lt(VoyageBookingTimeDO::getTimeValue, timeValue)
                .orderByDesc(VoyageBookingTimeDO::getTimeValue)
                .last("LIMIT 1")
        );
        return voyageBookingTime;
    }

    @Override
    public Map<String, String> getVoyageByVesselCodeAndVoyage(String vesselCode, String voyage, String polCode, String exImType) {
        List<VoyageBookingTimeDO> voyageBookingTimeList = voyageBookingTimeMapper.selectList(new LambdaQueryWrapperX<VoyageBookingTimeDO>()
                .eq(VoyageBookingTimeDO::getVesselCode, vesselCode)
                .eq(VoyageBookingTimeDO::getVoyage, voyage)
                .eq(VoyageBookingTimeDO::getPodCode, polCode)
                .eq(VoyageBookingTimeDO::getExImType, exImType)
        );
        return voyageBookingTimeList.stream().collect(Collectors.toMap(VoyageBookingTimeDO::getTimeType, VoyageBookingTimeDO::getTimeValue));
    }
}
